#pragma once


#include <QtCore/QObject>
#include <QtCore/QUuid>
#include <QtCore/QJsonObject>

#include "Export.hpp"
#include "QtStyle.hpp"
#include "QtNodeData.hpp"
#include "QtNodeGraphicsObject.hpp"
#include "QtConnectionGraphicsObject.hpp"
#include "QtSerializable.hpp"
#include "QLabel"
#include "QDialog"
#include "QDockWidget"
#include <unordered_map>

//NodeGeometry
#include <QtGui/QFontMetrics>

namespace Qt
{

	class QtConnection;
	class ConnectionState;
	class QtNodeGraphicsObject;
	class QtNodeDataModel;
	class StyleCollection;
	class QtNodePainterDelegate;

	class NODE_EDITOR_PUBLIC NodeGeometry
	{

	public:

		NodeGeometry(std::unique_ptr<QtNodeDataModel> const &dataModel);
		~NodeGeometry();
	public:
		unsigned int
			height() const { return _height; }

		void
			setHeight(unsigned int h) { _height = h; }

		unsigned int
			width() const { return _width; }

		void
			setWidth(unsigned int w) { _width = w; }

		unsigned int
			entryHeight() const { return _entryHeight; }
		void
			setEntryHeight(unsigned int h) { _entryHeight = h; }

		unsigned int
			entryWidth() const { return _entryWidth; }

		void
			setEntryWidth(unsigned int w) { _entryWidth = w; }

		

		unsigned int
			spacing() const { return _spacing; }

		unsigned int
			captionHeightSpacing() const { return _captionHeightSpacing; }

		void
			setSpacing(unsigned int s) { _spacing = s; }

		bool
			hovered() const { return _hovered; }

		void
			setHovered(unsigned int h) { _hovered = h; }

		unsigned int
			nSources() const;

		unsigned int
			nSinks() const;

		QPointF const&
			draggingPos() const
		{
			return _draggingPos;
		}

		void
			setDraggingPosition(QPointF const& pos)
		{
			_draggingPos = pos;
		}

		void leaveHoverButton() 
		{
			for (size_t i = 0; i < mHotKeyButtons.size(); i++)
			{
				mHotKeyButtons[i]->leaveWidget();
			}
		}

		QDialog* TipsWidget;
		QDockWidget* TipsWidget_;
		QDialog* PortTipsWidget;

		PortType hoverport_type;
		PortIndex hoverport_id;
		
		bool* isPortTipsShow;
		bool* isNodeTipsShow;

		void ShowTips()const;
		void HideTips()const;

		bool getPortTipsState()const;
		void ShowPortTips()const;
		void HidePortTips()const;

	public:

		QRectF
			entryBoundingRect() const;

		QRectF
			boundingRect() const;

		/// Updates size unconditionally
		void
			recalculateSize() const;

		/// Updates size if the QFontMetrics is changed
		void
			recalculateSize(QFont const &font) const;

		// TODO removed default QTransform()
		QPointF
			portScenePosition(PortIndex index,
				PortType portType,
				QTransform const & t = QTransform()) const;

		PortIndex
			checkHitScenePoint(PortType portType,
				QPointF point,
				QTransform const & t = QTransform()) const;

		PortIndex
			hoverHitScenePoint(PortType portType,
				QPointF point,
				QTransform const& t = QTransform()) const;

		PortIndex
			hoverHitPortArea(PortType portType,
				QPointF const scenePoint,
				QTransform const& sceneTransform,
				NodeGeometry const& geom,
				QtNodeDataModel const* mode) const;

		PortIndex
			//NodeGeometry::
			findHitPort(PortType portType,
				QPointF const scenePoint,
				QTransform const& sceneTransform,
				NodeGeometry const& geom,
				QtNodeDataModel const* model) const;

		int updateHotkeyHover( QPointF point) const;
		int updateMousePress(QPointF point) const;
		int updateMouseRelease(QPointF point) const;


		QRect
			resizeRect() const;

		/// Returns the position of a widget on the QtNode surface
		QPointF
			widgetPosition() const;

		/// Returns the maximum height a widget can be without causing the node to grow.
		int
			equivalentWidgetHeight() const;

		unsigned int
			validationHeight() const;

		unsigned int
			validationWidth() const;

		static
			QPointF
			calculateNodePositionBetweenNodePorts(PortIndex targetPortIndex, PortType targetPort, QtNode* targetNode,
				PortIndex sourcePortIndex, PortType sourcePort, QtNode* sourceNode,
				QtNode& newNode);

		unsigned int
			captionHeight() const;

		unsigned int
			statusBarHeight() const;

		unsigned int
			captionWidth() const;

		const std::vector<std::shared_ptr<HotkeyButton>>& constHotkeyButtons() const { return mHotKeyButtons; };

		bool isHotkeyChecked(unsigned int id) { return mHotKeyButtons[id]->isChecked(); }

	private:
		unsigned int
			portWidth(PortType portType) const;

		void pushHotkeyButton(std::shared_ptr<HotkeyButton> button)
		{
			mHotKeyButtons.push_back(button);
			buttonNum = mHotKeyButtons.size();
		}

		void recalculateHotkeyButtons()const;

		QSize getHotkeyButtonsSize()const;


	private:

		// some variables are mutable because
		// we need to change drawing metrics
		// corresponding to fontMetrics
		// but this doesn't change constness of QtNode

		mutable unsigned int _width;
		mutable unsigned int _height;
		unsigned int _entryWidth;
		mutable unsigned int _inputPortWidth;
		mutable unsigned int _outputPortWidth;
		mutable unsigned int _entryHeight;
		unsigned int _spacing;
		unsigned int _captionHeightSpacing;

		bool _hovered;

		unsigned int _nSources;
		unsigned int _nSinks;

		QPointF _draggingPos;

		std::unique_ptr<QtNodeDataModel> const &_dataModel;

		mutable QFontMetrics _fontMetrics;
		mutable QFontMetrics _boldFontMetrics;

		std::vector<std::shared_ptr<HotkeyButton>> mHotKeyButtons;
		unsigned int buttonNum = 0;

	};

	/// Contains vectors of connected input and output connections.
/// Stores bool for reacting on hovering connections
	class NODE_EDITOR_PUBLIC NodeState
	{
	public:
		enum ReactToConnectionState
		{
			REACTING,
			NOT_REACTING
		};

	public:

		NodeState(std::unique_ptr<QtNodeDataModel> const &model);

	public:

		using ConnectionPtrSet =
			std::unordered_map<QUuid, QtConnection*>;

		/// Returns vector of connections ID.
		/// Some of them can be empty (null)
		std::vector<ConnectionPtrSet> const&
			getEntries(PortType) const;

		std::vector<ConnectionPtrSet> &
			getEntries(PortType);

		ConnectionPtrSet
			connections(PortType portType, PortIndex portIndex) const;

		void
			setConnection(PortType portType,
				PortIndex portIndex,
				QtConnection& connection);

		void
			eraseConnection(PortType portType,
				PortIndex portIndex,
				QUuid id);

		ReactToConnectionState
			reaction() const;

		PortType
			reactingPortType() const;

		NodeDataType
			reactingDataType() const;

		void
			setReaction(ReactToConnectionState reaction,
				PortType reactingPortType = PortType::None,

				NodeDataType reactingDataType =
				NodeDataType());

		bool
			isReacting() const;

		void
			setResizing(bool resizing);

		bool
			resizing() const;

	private:

		std::vector<ConnectionPtrSet> _inConnections;
		std::vector<ConnectionPtrSet> _outConnections;

		ReactToConnectionState _reaction;
		PortType     _reactingPortType;
		NodeDataType _reactingDataType;

		bool _resizing;
	};

	class NODE_EDITOR_PUBLIC QtNode
		: public QObject
		, public QtSerializable
	{
		Q_OBJECT

	public:

		/// QtNodeDataModel should be an rvalue and is moved into the QtNode
		QtNode(std::unique_ptr<QtNodeDataModel> && dataModel);

		virtual
			~QtNode();

	public:

		QJsonObject
			save() const override;

		void
			restore(QJsonObject const &json) override;

	public:

		QUuid
			id() const;

		void reactToPossibleConnection(PortType,
			NodeDataType const &,
			QPointF const & scenePoint);

		void
			resetReactionToConnection();

	public:

		QtNodeGraphicsObject const &
			nodeGraphicsObject() const;

		QtNodeGraphicsObject &
			nodeGraphicsObject();

		void
			setGraphicsObject(std::unique_ptr<QtNodeGraphicsObject>&& graphics);

		NodeGeometry&
			nodeGeometry();

		NodeGeometry const&
			nodeGeometry() const;

		NodeState const &
			nodeState() const;

		NodeState &
			nodeState();

		QtNodeDataModel*
			nodeDataModel() const;

	public Q_SLOTS: // data propagation

	  /// Propagates incoming data to the underlying model.
		void
			propagateData(std::shared_ptr<QtNodeData> nodeData,
				PortIndex inPortIndex) const;

		/// Fetches data from model's OUT #index port
		/// and propagates it to the connection
		void
			onDataUpdated(PortIndex index);

		/// update the graphic part if the size of the embeddedwidget changes
		void
			onNodeSizeUpdated();

	private:

		// addressing

		QUuid _uid;

		// data

		std::unique_ptr<QtNodeDataModel> _nodeDataModel;

		NodeState _nodeState;

		// painting

		NodeGeometry _nodeGeometry;

		std::unique_ptr<QtNodeGraphicsObject> _nodeGraphicsObject;
	};
}
